package database;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import database.datastructure.BPlusTree;

/**
 * This class represents a B+ tree index over records in a file using a chosen column.
 * @author sravanku
 *
 */
public class Index implements Serializable{
	
	private static final long serialVersionUID = 8389325791815741752L;
	
	private BPlusTree bTree = null;
	private String indexName = "";
	private String columnName = "";
	private Map<Integer,Map<String, String>> recordMap = null;
	private static int no_of_records = 0;
	/**
	 * Used for createIndex
	 * @param indexName
	 * @param recordMap
	 * @param column
	 */
	public Index(String indexName, Map<Integer,Map<String, String>> recordMap, String column, String tableName, int no_of_records) {
		bTree = new BPlusTree();
		this.recordMap = recordMap;
		this.columnName = column;
		this.indexName = tableName+"_"+indexName;
		this.no_of_records = no_of_records;
		buildIndex();
	}
	
	/**
	 * Used for restoreIndex
	 * @param indexName2
	 */
	public Index(String tableName, String indexName2) {
		try {
			//System.out.println("In Index : TableName : "+tableName+" IndexName2 : "+indexName2);
			ObjectInputStream idxObjIp = new ObjectInputStream(new FileInputStream(tableName+"_"+indexName2+".idx"));
			/**
			 * code fix: should get Index instead of BPlusTree
			 * 
			 * this.bTree = (BPlusTree) idxObjIp.readObject();
			 */
			Index idx = (Index) idxObjIp.readObject();
			this.bTree = idx.getbTree();
			idxObjIp.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Used to Insert to B+-Tree
	 */
	public void buildIndex() {
		for (int recId:recordMap.keySet()) {
			Map<String, String> record = recordMap.get(recId);
			bTree.insert(record.get(columnName), recId);
		}
	}
	
	/**
	 * Inserting a new record to index
	 */
	public void insertRecordToIndex(String key, int value) {
		try {
			bTree.insert(key, value);
			no_of_records++;
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * write index contents to disk. If that index had already been saved before then overwrite the previous save.
	 */
	public void dumpIndex(){
		try {
			System.out.println("Index File Created with Name : "+indexName+".idx");
			File newIndexFile = new File(indexName+".idx");
			newIndexFile.createNewFile();
			ObjectOutputStream idxObjOp = new ObjectOutputStream(new FileOutputStream(newIndexFile));
			//String indexContents = "Index "+indexName+" over "+columnName+"\n\n"+bTree.printTree();
			idxObjOp.writeObject(this);
			idxObjOp.close();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 *  Deletes the index for a given indexName
	 * @param tableName
	 * @param columnName
	 * @return
	 */
	public static boolean deleteIndex(String tableName, String indexToBeDeleted) {
		//DataManager.indexRefs.remove(key)
		return new File(tableName+"_"+indexToBeDeleted+".idx").delete();
	}
	
	/**
	 * Print the contents of the index in a human-readable format
	 */
	public String viewIndex(){
		String indexContents = "Index "+indexName+" over "+columnName+"\n\n"+bTree.newprintTree();
		return indexContents;
	}
	
	/**
	 * 
	 */
	public void deleteKeysFromIndex(String key) {
		bTree.deleteKey(key);
	}
	
	/**
	 * Return new IndexIterator, the Iterator should only traverse over records whose value in the indexed column is equal to key.
	 * @param key
	 * @return
	 */
	public Iterator<Integer> iterator(String key){
		return new IndexIterator(key);
	}
	
	public BPlusTree getbTree() {
		return bTree;
	}

	/**
	 * Similar to FileIterator, also needs to implement the Iterator interface methods: remove must be supported. 
	 * The order of the traversal with use of this Iterator must be consistent with the index and the key
	 * @author sravanku
	 *
	 */
	private class IndexIterator implements Iterator<Integer>{

		/*
		 * Other details:
		 * There may be many iterators �opened� for the same file or index at the same time. 
		 * In situation when between iterating steps index or file content is changed by some other iterator or 
		 * method throw ConcurrentModificationException when the next next() or remove() is called.
		 * Record Identifiers should be nonnegative integers.
		 * You will need to add additional methods to above mentioned classes in order for the methods that are needed be able to work 
		 * (e.g. you will need constructors for DataFile and Index).
		 *  Also you may choose to add some other methods, in case you would find them helpful.
		 * (non-Javadoc)
		 * @see java.util.Iterator#hasNext()
		 */
		
		int curPos = 0;
		int lastPos = -1;
		ArrayList<Integer> searchList = null;
		ArrayList<Integer> deletedList = null;
		int iter_no_of_records = no_of_records;
		
		public boolean isModifiedConcurrently(){
			if(no_of_records == iter_no_of_records){
				return false;
			}
			return true;
		}
		
		IndexIterator(String key) {
			try {
				searchList = new ArrayList<Integer>();
				deletedList = new ArrayList<Integer>();
				ArrayList<Integer> tempList = (ArrayList<Integer>)bTree.search(key);
				
				for(Integer id:tempList){
					// remove if id is not present in recordMap
					if(!recordMap.containsKey(id)){
						//searchList.remove(id);
						continue;
					}
					else if(recordMap.get(id).containsKey(DataFile.__IS_DELETED__) && recordMap.get(id).get(DataFile.__IS_DELETED__).equals(DataFile.__IS_DELETED__VAL)){
						//searchList.remove(id);
						continue;
					}
					else{
						searchList.add(id);
					}
					
				}
				
				/*if (searchList.size() != 0) {
					checkDeleted();
					 for (int i = 0; i < deletedList.size(); i++)
						System.out.println(deletedList.get(i)); 
					if (deletedList.size() != 0) {
						reBuildIterator(); //--- Getting Error while trying to delete entry from listIter
						for (int i = 0; i < searchList.size(); i++) {
							//System.out.println("Size after deleting : "+listIter.get(i));
						}
					}
					
				}*/
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		/**
		 * Currently used only in constructor.
		 * Check everything before this is called from other places.
		 */
		public void checkDeleted() {
			for (int i = 0; i < searchList.size(); i++) {
				int curRecordId = 0;
				curRecordId = searchList.get(i);
				//System.out.println("i : "+i+" Element at "+i+" : "+listIter.get(i));
				//System.out.println("Current Record Id : "+curRecordId);
				Map<String, String> curRec = recordMap.get(curRecordId);
				int toBeRemovedCntr = 0;
				for (Map.Entry entry: curRec.entrySet()) {
					if (entry.getKey().equals("__IS_DELETED__")) {
						//System.out.println("Working for : "+curRecordId);
						toBeRemovedCntr++;
					} else {
						continue;
					}
				}
				if (toBeRemovedCntr != 0) {
					deletedList.add(curRecordId);
				} else {
					continue;
				}
			}
		}
		
		public void reBuildIterator() {
			try {
				if (deletedList.size() != 0) {
					int iterSize = searchList.size();
					if (iterSize != 0) {
						for (int i = 0; i < deletedList.size(); i++) {
							int element = deletedList.get(i);
							for (int j = 0; j < searchList.size(); j++) {
								if (element == searchList.get(j)) {
									searchList.remove(j);
									break;
								}
							}
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		@Override
		public boolean hasNext() {
			return curPos != searchList.size();
		}

		@Override
		public Integer next() {
			if(isModifiedConcurrently()){
				throw new ConcurrentModificationException("Data modified by another iterator");
			}
			try {
				int next = searchList.get(curPos);
				lastPos = curPos;
				curPos++;
				return next;
			} catch(IndexOutOfBoundsException e) {
				throw new NoSuchElementException();
			}
		}

		@Override
		public void remove() {
			if(isModifiedConcurrently()){
				throw new ConcurrentModificationException("Data modified by another iterator");
			}
			if (lastPos < 0) {
				throw new IllegalStateException();
			}
			try {
				/**
				 * 	Need to mark the data Record flag as deleted
				 * ---Done
				 */
				int delRecordId = searchList.get(lastPos); 	
				searchList.remove(lastPos);
				Map<String, String> recordToBeDeleted = recordMap.get(delRecordId);
				recordToBeDeleted.put(DataFile.__IS_DELETED__, DataFile.__IS_DELETED__VAL); 
				// Will these have to be included any where??
				// Ideally No, Vathsan to confirm.
				no_of_records--;
				iter_no_of_records--;
				
				//System.out.println("Removed Record with recordId: "+delRecordId);
				if (lastPos < curPos)
					curPos--;
			} catch (IndexOutOfBoundsException e) {
				throw new ConcurrentModificationException();
			}
		}
	}
	
}
